﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net.Security;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;

namespace ManyinEngine
{
    public class Graph : IEnumerable<Node>
    {
        private List<Node> _nodes;
        private StringBuilder _stringBuilderErrors;


        public List<Node> Nodes
        {
            get
            {
                if (_nodes == null)
                {
                    _nodes = new List<Node>();
                }
                return _nodes;
            }
            set { _nodes = value; }
        }

        public StringBuilder StringBuilderErrors
        {
            get { return _stringBuilderErrors ?? (_stringBuilderErrors = new StringBuilder()); }
        }

        public String Errors
        {
            get { return _stringBuilderErrors.ToString(); }
        }



        public bool IsValid()
        {
            
            var ret = true;
            foreach (var node in Nodes)
            {
                if (node[Edges.Top].HasValue && node[Edges.RightTop].HasValue)
                {
                    if (!_nodes[node[Edges.Top].Value][Edges.RightBot].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.Top] + " should have a RightBot edge.");
                        ret = false;
                        break;
                    }
                    if (!_nodes[node[Edges.RightTop].Value][Edges.LeftTop].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.RightTop] + " should have a LeftTop edge.");
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.Top].Value][Edges.RightBot].Value != node[Edges.RightTop].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.RightTop] + " should be RightBot of node " + node[Edges.Top]);
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.RightTop].Value][Edges.LeftTop].Value != node[Edges.Top].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.Top] + " should be LeftTop of node " + node[Edges.RightTop]);
                        ret = false;
                        break;
                    }
                }

                if (node[Edges.RightTop].HasValue && node[Edges.RightBot].HasValue)
                {
                    if (!_nodes[node[Edges.RightTop].Value][Edges.Bot].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.RightTop] + " should have a Bot edge.");
                        ret = false;
                        break;
                    }
                    if (!_nodes[node[Edges.RightBot].Value][Edges.Top].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.RightBot] + " should have a Top edge.");
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.RightTop].Value][Edges.Bot].Value != node[Edges.RightBot].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.RightBot] + " should be Bot of node " + node[Edges.RightTop]);
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.RightBot].Value][Edges.Top].Value != node[Edges.RightTop].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.RightTop] + " should be Top of node " + node[Edges.RightBot]);
                        ret = false;
                        break;
                    }
                }

                if (node[Edges.RightBot].HasValue && node[Edges.Bot].HasValue)
                {
                    if (!_nodes[node[Edges.RightBot].Value][Edges.LeftBot].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.RightBot] + " should have a LeftBot edge.");
                        ret = false;
                        break;
                    }
                    if (!_nodes[node[Edges.Bot].Value][Edges.RightTop].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.Bot] + " should have a RightTop edge.");
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.RightBot].Value][Edges.LeftBot].Value != node[Edges.Bot].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.Bot] + " should be LeftBot of node " + node[Edges.RightBot]);
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.Bot].Value][Edges.RightTop].Value != node[Edges.RightBot].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.RightBot] + " should be RightTop of node " + node[Edges.Bot]);
                        ret = false;
                        break;
                    }
                }

                if (node[Edges.Bot].HasValue && node[Edges.LeftBot].HasValue)
                {
                    if (!_nodes[node[Edges.Bot].Value][Edges.LeftTop].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.Bot] + " should have a LeftTop edge.");
                        ret = false;
                        break;
                    }
                    if (!_nodes[node[Edges.LeftBot].Value][Edges.RightBot].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.LeftBot] + " should have a RightBot edge.");
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.Bot].Value][Edges.LeftTop].Value != node[Edges.LeftBot].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.LeftBot] + " should be LeftTop of node " + node[Edges.Bot]);
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.LeftBot].Value][Edges.RightBot].Value != node[Edges.Bot].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.Bot] + " should be RightBot of node " + node[Edges.LeftBot]);
                        ret = false;
                        break;
                    }
                }

                if (node[Edges.LeftBot].HasValue && node[Edges.LeftTop].HasValue)
                {
                    if (!_nodes[node[Edges.LeftBot].Value][Edges.Top].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.LeftBot] + " should have a Top edge.");
                        ret = false;
                        break;
                    }
                    if (!_nodes[node[Edges.LeftTop].Value][Edges.Bot].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.LeftTop] + " should have a Bot edge.");
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.LeftBot].Value][Edges.Top].Value != node[Edges.LeftTop].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.LeftTop] + " should be Top of node " + node[Edges.LeftBot]);
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.LeftTop].Value][Edges.Bot].Value != node[Edges.LeftBot].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.LeftBot] + " should be Bot of node " + node[Edges.LeftTop]);
                        ret = false;
                        break;
                    }
                }

                if (node[Edges.LeftTop].HasValue && node[Edges.Top].HasValue)
                {
                    if (!_nodes[node[Edges.LeftTop].Value][Edges.RightTop].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.LeftTop] + " should have a RightTop edge.");
                        ret = false;
                        break;
                    }
                    if (!_nodes[node[Edges.Top].Value][Edges.LeftBot].HasValue)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.Top] + " should have a LeftBot edge.");
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.LeftTop].Value][Edges.RightTop].Value != node[Edges.Top].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.Top] + " should be RightTop of node " + node[Edges.LeftTop]);
                        ret = false;
                        break;
                    }
                    if (_nodes[node[Edges.Top].Value][Edges.LeftBot].Value != node[Edges.LeftTop].Value)
                    {
                        StringBuilderErrors.AppendLine("[INVALID GRAPH] first error detected: node " + node[Edges.LeftTop] + " should be LeftBot of node " + node[Edges.Top]);
                        ret = false;
                        break;
                    }
                }

            }
            return ret;
        }

        public Graph()
        {
            _nodes = new List<Node>();
        }

        public void Add(Node node = null)
        {
            if (node == null)
            {
                node = new Node();
                node.Index = _nodes.Count;
                node.Name = "node " + node.Index;
            }
            _nodes.Add(node);
        }

        public IEnumerator<Node> GetEnumerator()
        {
            return _nodes.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public Node this[int index]
        {
            get { return _nodes.FirstOrDefault(node => node.Index == index); }
        }




        /// <summary>
        /// Returns a list with de neighbours of the node with the specified index.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public List<Node> GetNeighbours(Node selNode)
        {
            var lstToReturn = new List<Node>();
            if (selNode != null)
            {
                foreach (Edges value in Enum.GetValues(typeof(Edges)))
                {
                    if (selNode[value].HasValue)
                    {
                        lstToReturn.Add(this[selNode[value].Value]);
                    }
                }

            }
            
            return lstToReturn;
        }


        public List<Node> GetReachableNodesAtJumps(int index, int jumps)
        {
            var selNode = Nodes.FirstOrDefault(node => node.Index == index);
            var lstToReturn = new List<Node>();
            if (selNode != null)
            {
                for (int i = 0; i < jumps; i++)
                {
                    var lst = GetNeighbours(selNode);
                    
                }
            }
            return lstToReturn;
        }

        /// <summary>
        /// Adds a node in a mysterious way.
        /// </summary>
        public void AddNodeMysterious()
        {

            Node node = new Node();
            node.Index = _nodes.Count;
            node.Name = "node " + node.Index;
            if (Nodes.Count <= 0)
            {
                Nodes.Add(node);
            }
            else
            {

                foreach (var selNode in Nodes)
                {
                    bool added = false;
                    foreach (Edges value in Enum.GetValues(typeof(Edges)))
                    {
                        if (!selNode[value].HasValue)
                        {
                            AddNode(selNode, value, node);
                            added = true;
                            break;
                        }
                    }
                    if (added)
                    {
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Adds nodeToInsert to the graph in the edge of the referenceNode
        /// </summary>
        /// <param name="referenceNode"></param>
        /// <param name="edge"></param>
        /// <param name="nodeToInsert"></param>
        public void AddNode(Node referenceNode, Edges edge, Node nodeToInsert)
        {
            if (this[referenceNode.Index] == null)
            {
                StringBuilderErrors.AppendLine("[Add Error] el nodo " + referenceNode.Name +
                                               "no se encuentra en el grafo");
                return;
            }
            if (this[nodeToInsert.Index] != null)
            {
                StringBuilderErrors.AppendLine("[Add Error] el nodo " + nodeToInsert.Name +
                                               "ya se encuentra en el grafo");
            }
            else
            {
                Nodes.Add(nodeToInsert);
            }

            referenceNode[edge] = nodeToInsert.Index;
            nodeToInsert[GetOppositeEdge(edge)] = referenceNode.Index;
            if (referenceNode[GetPreviousEdge(edge)].HasValue)
            {
                int prevEdge = referenceNode[GetPreviousEdge(edge)].Value;
                if (this[prevEdge] != null )
                {
                    var auxNode = this[prevEdge];
                    if (!auxNode[GetOppositeEdge(GetNextEdge(GetOppositeEdge(edge)))].HasValue)
                    {
                        AddNode(auxNode, GetOppositeEdge(GetNextEdge(GetOppositeEdge(edge))), nodeToInsert);
                    }
                    else if (auxNode[GetOppositeEdge(GetNextEdge(GetOppositeEdge(edge)))].Value != nodeToInsert.Index)
                    {
                        AddNode(auxNode, GetOppositeEdge(GetNextEdge(GetOppositeEdge(edge))), nodeToInsert);
                    }
                }
            }

            if (referenceNode[GetNextEdge(edge)].HasValue)
            {
                int prevEdge = referenceNode[GetNextEdge(edge)].Value;
                if (this[prevEdge] != null)
                {
                    var auxNode = this[prevEdge];
                    AddNode(auxNode, GetOppositeEdge(GetPreviousEdge(GetOppositeEdge(edge))), nodeToInsert);
                }
            }
        }



        public Edges GetOppositeEdge(Edges edge)
        {
            switch (edge)
            {
                case Edges.Top:
                    return Edges.Bot;
                case Edges.RightTop:
                    return Edges.LeftBot;
                case Edges.RightBot:
                    return Edges.LeftTop;
                case Edges.Bot:
                    return Edges.Top;
                case Edges.LeftBot:
                    return Edges.RightTop;
                case Edges.LeftTop:
                    return Edges.RightBot;
                default:
                    throw new ArgumentOutOfRangeException("edge");
            }
        }

        public Edges GetPreviousEdge(Edges edge)
        {
            switch (edge)
            {
                case Edges.Top:
                    return Edges.LeftTop;
                case Edges.RightTop:
                    return Edges.Top;
                case Edges.RightBot:
                    return Edges.RightTop;
                case Edges.Bot:
                    return Edges.RightBot;
                case Edges.LeftBot:
                    return Edges.Bot;
                case Edges.LeftTop:
                    return Edges.LeftBot;
                default:
                    throw new ArgumentOutOfRangeException("edge");
            }
        }

        public Edges GetNextEdge(Edges edge)
        {
            switch (edge)
            {
                case Edges.Top:
                    return Edges.RightTop;
                case Edges.RightTop:
                    return Edges.RightBot;
                case Edges.RightBot:
                    return Edges.Bot;
                case Edges.Bot:
                    return Edges.LeftBot;
                case Edges.LeftBot:
                    return Edges.LeftTop;
                case Edges.LeftTop:
                    return Edges.Top;
                default:
                    throw new ArgumentOutOfRangeException("edge");
            }
        }
    }
}
